package com.jhsw.roadsun.module.business.service.expressage;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.StyleSet;
import cn.idev.excel.EasyExcel;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.metadata.fill.FillConfig;
import com.jhsw.roadsun.framework.common.pojo.PageResult;
import com.jhsw.roadsun.framework.common.util.object.BeanUtils;
import com.jhsw.roadsun.framework.dict.core.DictFrameworkUtils;
import com.jhsw.roadsun.module.business.controller.admin.expressage.vo.ExpressageDetailRespVO;
import com.jhsw.roadsun.module.business.controller.admin.expressage.vo.ExpressagePageReqVO;
import com.jhsw.roadsun.module.business.controller.admin.expressage.vo.ExpressageRespVO;
import com.jhsw.roadsun.module.business.controller.admin.expressage.vo.ExpressageSaveReqVO;
import com.jhsw.roadsun.module.business.controller.admin.expressagegoods.vo.ExpressageGoodsUpdateVO;
import com.jhsw.roadsun.module.business.controller.app.expressage.vo.AppExpressagePageReqVO;
import com.jhsw.roadsun.module.business.controller.app.expressage.vo.AppExpressageRespVO;
import com.jhsw.roadsun.module.business.controller.app.expressage.vo.AppExpressageSaveReqVO;
import com.jhsw.roadsun.module.business.controller.app.expressage.vo.AppExpressageSimpleRespVO;
import com.jhsw.roadsun.module.business.controller.app.expressage.vo.goods.AppExpressageGoodsRespVO;
import com.jhsw.roadsun.module.business.controller.app.expressage.vo.goods.AppExpressageGoodsSaveReqVO;
import com.jhsw.roadsun.module.business.dal.dataobject.expressage.ExpressageDO;
import com.jhsw.roadsun.module.business.dal.dataobject.expressage.dto.ExpressageInvoiceDTO;
import com.jhsw.roadsun.module.business.dal.dataobject.expressagegoods.ExpressageGoodsDO;
import com.jhsw.roadsun.module.business.dal.dataobject.user.BizUserDO;
import com.jhsw.roadsun.module.business.dal.mysql.expressage.ExpressageMapper;
import com.jhsw.roadsun.module.business.dal.mysql.expressagegoods.ExpressageGoodsMapper;
import com.jhsw.roadsun.module.business.enums.DictTypeConstants;
import com.jhsw.roadsun.module.business.service.config.BizConfigService;
import com.jhsw.roadsun.module.business.service.user.BizUserService;
import com.jhsw.roadsun.module.business.util.BarCodeUtil;
import com.jhsw.roadsun.module.business.util.PoolUtil;
import com.jhsw.roadsun.module.business.util.WordToPdfUtil;
import com.jhsw.roadsun.module.infra.api.file.FileApi;
import com.jhsw.roadsun.module.system.api.user.AdminUserApi;
import com.jhsw.roadsun.module.system.api.user.dto.AdminUserRespDTO;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import static com.jhsw.roadsun.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jhsw.roadsun.module.business.enums.ErrorCodeConstants.*;

/**
 * 寄件信息 Service 实现类
 *
 * @author Ysugar
 */
@Slf4j
@Service
@Validated
public class ExpressageServiceImpl implements ExpressageService {

    @Resource
    private ExpressageMapper expressageMapper;
    @Resource
    private ExpressageGoodsMapper expressageGoodsMapper;
    @Resource
    @Lazy
    private BizUserService userService;
    @Resource
    private BizConfigService configService;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private FileApi fileApi;
    @Resource
    private WordToPdfUtil wordToPdfUtil;

    @Value("${download-temp-dir}")
    private String downloadTempDir;

    private static final ReentrantLock CREATE_LOCK = new ReentrantLock();

    @Override
    public Long createExpressage(ExpressageSaveReqVO createReqVO) {
        // 插入
        ExpressageDO expressage = BeanUtils.toBean(createReqVO, ExpressageDO.class);
        expressageMapper.insert(expressage);

        // 返回
        return expressage.getId();
    }

    @Override
    @Transactional
    public void updateExpressage(ExpressageSaveReqVO updateReqVO, Long adminUserId) {
        // 校验存在
        validateExpressageExists(updateReqVO.getId());
        // 更新
        ExpressageDO updateObj = BeanUtils.toBean(updateReqVO, ExpressageDO.class);
        updateObj.setAuditAdminUserId(adminUserId).setAuditDatetime(LocalDateTime.now());

        try {
            // 判断是否是审核通过，若是，则生成快递 pdf和excel
            if (updateObj.getAuditStatus() == 1){
                List<Long> ids = Collections.singletonList(updateReqVO.getId());
                updateObj.setWaybillPdf(downloadFile(1, ids))
                        .setInvoicePdf(downloadFile(2, ids))
                        .setInvoiceExcel(downloadFile(3, ids));
            }
        }catch (Throwable e){
            log.error("出单生成Pdf与Excel失败：", e);
        }

        expressageMapper.updateById(updateObj);

        // 更新货物信息
        List<ExpressageGoodsUpdateVO> goods = updateReqVO.getGoods();
        for (ExpressageGoodsUpdateVO good : goods) {
            expressageGoodsMapper.updateById(BeanUtils.toBean(good, ExpressageGoodsDO.class));
        }
    }

    @Override
    public void deleteExpressage(Long id) {
        // 校验存在
        validateExpressageExists(id);
        // 删除
        expressageMapper.deleteById(id);
    }

    @Override
    public void deleteExpressageListByIds(List<Long> ids) {
        // 删除
        expressageMapper.deleteByIds(ids);
    }


    private ExpressageDO validateExpressageExists(Long id) {
        ExpressageDO expressageDO = expressageMapper.selectById(id);
        if (expressageDO == null) {
            throw exception(EXPRESSAGE_NOT_EXISTS);
        }
        return expressageDO;
    }

    @Override
    public ExpressageDetailRespVO getExpressage(Long id) {
        ExpressageDO expressageDO = expressageMapper.selectById(id);
        ExpressageDetailRespVO respVO = BeanUtils.toBean(expressageDO, ExpressageDetailRespVO.class);
        if (Objects.nonNull(respVO)) {
            // 设置填写人信息
            BizUserDO bizUser = userService.getBizUser(respVO.getUserId());
            if (Objects.nonNull(bizUser))
                respVO.setNickname(bizUser.getNickname()).setMobile(bizUser.getMobile());
            Long auditAdminUserId = respVO.getAuditAdminUserId();

            // 设置审核员信息
            if (Objects.nonNull(auditAdminUserId)) {
                AdminUserRespDTO adminUser = adminUserApi.getUser(auditAdminUserId);
                if (Objects.nonNull(adminUser))
                    respVO.setAuditAdminUsername(adminUser.getNickname()).setMobile(adminUser.getMobile());
            }
            // 查询货物信息
            List<ExpressageGoodsDO> goods = expressageGoodsMapper.selectList(ExpressageGoodsDO::getExpressageId, id);
            respVO.setGoods(BeanUtils.toBean(goods, ExpressageGoodsUpdateVO.class));
        }
        return respVO;
    }

    @Override
    public ExpressageDO getExpressageValidate(Long id) {
        return validateExpressageExists(id);
    }

    @Override
    public PageResult<ExpressageRespVO> getExpressagePage(ExpressagePageReqVO pageReqVO) {
        return expressageMapper.selectPage(pageReqVO);
    }

    @Override
    public String downloadFile(Integer type, List<Long> ids) {
        // 去重
        ids = ids.stream().distinct().collect(Collectors.toList());

        // 创建临时压缩包目录
        String tempZipDir = downloadTempDir + System.currentTimeMillis() +"/";
        File tempZipDirFile = FileUtil.mkdir(tempZipDir);

        CountDownLatch countDownLatch = new CountDownLatch(ids.size());

        List<String> results = new ArrayList<>();
        for (Long id : ids) {
            PoolUtil.exec(() -> {
                try {
                    // 根据ID获取订单信息
                    ExpressageDO expressageDO = expressageMapper.selectById(id);

                    // 判断当前订单是否是已出单状态，是才生成
                    if (!expressageDO.getAuditStatus().equals(1))
                        return;

                    // 获取订单货物信息
                    List<ExpressageGoodsDO> goods = expressageGoodsMapper.selectList(ExpressageGoodsDO::getExpressageId, id);

                    // pdf面单
                    if (type.equals(1)) {
                        // 先获取是否已经有数据，若有，则直接返回
                        String dbWaybillPdf = expressageDO.getWaybillPdf();
                        if (StrUtil.isNotBlank(dbWaybillPdf)){
                            results.add(dbWaybillPdf);
                            HttpUtil.downloadFile(dbWaybillPdf, tempZipDir + expressageDO.getOrderNo() + ".pdf");
                        }else {
                            // 获取Word模板，模板存放路径在项目的resources目录下
                            ClassPathResource resource = new ClassPathResource("template/md_word.docx");
                            // 注册xdocreport实例并加载FreeMarker模板引擎
                            IXDocReport report = XDocReportRegistry.getRegistry().loadReport(resource.getStream(), TemplateEngineKind.Freemarker);
                            // 创建xdocreport上下文对象
                            IContext context = report.createContext();

                            //创建要替换的文本变量
                            context.put("orderNo", getNormal(expressageDO.getOrderNo()));
                            context.put("sendName", getNormal(expressageDO.getSendName()));
                            context.put("sendMobile", getNormal(expressageDO.getSendMobile()));
                            context.put("sendAddress", getNormal(expressageDO.getSendAddress()));
                            context.put("sendCompany", getNormal(expressageDO.getSendCompany()));
                            context.put("recipientsName", getNormal(expressageDO.getRecipientsName()));
                            context.put("recipientsMobile", getNormal(expressageDO.getRecipientsMobile()));
                            context.put("recipientsAddress", getNormal(expressageDO.getRecipientsAddress()));
                            context.put("recipientsCompany", getNormal(expressageDO.getRecipientsCompany()));
                            context.put("customerAccount", getNormal(expressageDO.getCustomerAccount()));
                            context.put("expressageOrder", getNormal(expressageDO.getExpressageOrder()));
                            context.put("totalNum", goods.stream().mapToLong(ExpressageGoodsDO::getNum).sum());
                            LocalDateTime createTime = expressageDO.getCreateTime();
                            context.put("year", String.valueOf(createTime.getYear()));
                            context.put("month", createTime.getMonthValue());
                            context.put("day", createTime.getDayOfMonth());

                            String shippingType = expressageDO.getShippingType();
                            context.put("shippingType1", "空运".equals(shippingType) ? "☑" : "☐");
                            context.put("shippingType2", "陆运".equals(shippingType) ? "☑" : "☐");
                            context.put("shippingType3", "海运".equals(shippingType) ? "☑" : "☐");

                            Integer payMethod = expressageDO.getPayMethod();
                            context.put("payMethod1", payMethod.equals(0) ? "☑" : "☐");
                            context.put("payMethod2", payMethod.equals(1) ? "☑" : "☐");
                            context.put("payMethod3", payMethod.equals(2) ? "☑" : "☐");

                            // 创建字段元数据
                            FieldsMetadata fm = report.createFieldsMetadata();

                            // 根据订单号生成条形码
                            byte[] barCodeBytes = BarCodeUtil.generateToByte(expressageDO.getOrderNo());

                            try (ByteArrayInputStream bis = new ByteArrayInputStream(barCodeBytes)){
                                fm.addFieldAsImage("qrCode");
                                context.put("qrCode", bis);

                                //输出到本地目录
                                File tempWordFile = new File(tempZipDir + expressageDO.getOrderNo() + ".docx");
                                FileOutputStream out = new FileOutputStream(tempWordFile);
                                report.process(context, out);

                                // 将word转为 pdf
                                String waybillPdf = fileApi.createFile(wordToPdfUtil.wordToPdf(tempWordFile, tempZipDir), expressageDO.getOrderNo() + ".pdf", "downloadFile", null);
                                // 更新当前记录里的数据
                                expressageMapper.update(ExpressageDO::getId, id, ExpressageDO::getWaybillPdf, waybillPdf);

                                // 将数据添加到总结果里
                                results.add(waybillPdf);
                            }
                        }
                    } else {
                        BigDecimal totalAmount = CollUtil.isNotEmpty(goods)
                                ? BigDecimal.valueOf(goods.stream().mapToDouble(o -> o.getTotalPrice().doubleValue()).sum())
                                : BigDecimal.ZERO;

                        // pdf发票
                        if (type.equals(2)) {
                            // 先获取是否已经有数据，若有，则直接返回
                            String dbInvoicePdf = expressageDO.getInvoicePdf();
                            if (StrUtil.isNotBlank(dbInvoicePdf)){
                                results.add(dbInvoicePdf);
                                HttpUtil.downloadFile(dbInvoicePdf, tempZipDir + expressageDO.getOrderNo() + ".pdf");
                            } else {
                                // 获取Word模板，模板存放路径在项目的resources目录下
                                ClassPathResource resource = new ClassPathResource("template/fp_word.docx");
                                // 注册xdocreport实例并加载FreeMarker模板引擎
                                IXDocReport report = XDocReportRegistry.getRegistry().loadReport(resource.getStream(), TemplateEngineKind.Freemarker);
                                // 创建xdocreport上下文对象
                                IContext context = report.createContext();

                                //创建要替换的文本变量
                                context.put("orderNo", getNormal(expressageDO.getOrderNo()));
                                context.put("invoiceNum", getNormal(expressageDO.getInvoiceNum()));
                                context.put("sendName", getNormal(expressageDO.getSendName()));
                                context.put("sendMobile", getNormal(expressageDO.getSendMobile()));
                                context.put("sendAddress", getNormal(expressageDO.getSendAddress()));
                                context.put("sendCompany", getNormal(expressageDO.getSendCompany()));
                                context.put("recipientsName", getNormal(expressageDO.getRecipientsName()));
                                context.put("recipientsMobile", getNormal(expressageDO.getRecipientsMobile()));
                                context.put("recipientsAddress", getNormal(expressageDO.getRecipientsAddress()));
                                context.put("recipientsCompany", getNormal(expressageDO.getRecipientsCompany()));
                                context.put("totalAmount", totalAmount);
                                context.put("goods", goods);

                                // 创建字段元数据
                                FieldsMetadata fm = report.createFieldsMetadata();
                                // Word模板中的表格数据对应的集合类型
                                fm.load("goods", ExpressageGoodsDO.class, true);

                                //输出到本地目录
                                File tempWordFile = new File(tempZipDir + expressageDO.getOrderNo() + ".docx");
                                FileOutputStream out = new FileOutputStream(tempWordFile);
                                report.process(context, out);

                                // 将word转为 pdf
                                String invoicePdf = fileApi.createFile(wordToPdfUtil.wordToPdf(tempWordFile, tempZipDir), expressageDO.getOrderNo() + ".pdf", "downloadFile", null);
                                // 更新当前记录里的数据
                                expressageMapper.update(ExpressageDO::getId, id, ExpressageDO::getInvoicePdf, invoicePdf);

                                // 将数据添加到总结果里
                                results.add(invoicePdf);
                            }
                        } else { // excel发票
                            // 先获取是否已经有数据，若有，则直接返回
                            String dbInvoiceExcel = expressageDO.getInvoiceExcel();
                            if (StrUtil.isNotBlank(dbInvoiceExcel)){
                                results.add(dbInvoiceExcel);
                                HttpUtil.downloadFile(dbInvoiceExcel, tempZipDir + expressageDO.getOrderNo() + ".xlsx");
                            } else {
                                // 获取excel模板
                                ClassPathResource resource = new ClassPathResource("template/fp_excel.xlsx");

                                try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                                     ExcelWriter excelWriter = EasyExcel.write(byteArrayOutputStream).withTemplate(resource.getStream()).build()) {
                                    WriteSheet writeSheet = EasyExcel.writerSheet("INV").build();

                                    FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
                                    excelWriter.fill(goods, fillConfig, writeSheet);
                                    // 转换对象，并赋值
                                    ExpressageInvoiceDTO invoiceDTO = BeanUtils.toBean(expressageDO, ExpressageInvoiceDTO.class);
                                    invoiceDTO.setTotalAmount(totalAmount);
                                    excelWriter.fill(invoiceDTO, writeSheet);
                                    excelWriter.finish();

                                    byte[] xlsxBytes = byteArrayOutputStream.toByteArray();

                                    FileUtil.writeBytes(xlsxBytes, tempZipDir + expressageDO.getOrderNo() + ".xlsx");

                                    String invoiceExcel = fileApi.createFile(xlsxBytes, expressageDO.getOrderNo() + ".xlsx", "downloadFile", null);

                                    // 更新当前记录里的数据
                                    expressageMapper.update(ExpressageDO::getId, id, ExpressageDO::getInvoiceExcel, invoiceExcel);

                                    // 将数据添加到总结果里
                                    results.add(invoiceExcel);
                                } catch (Throwable e) {
                                    log.error("[downloadFile][type({}) 创建文件异常]", type, e);
                                }
                            }
                        }
                    }
                }catch (Throwable e){
                    log.error("[downloadFile][type({}) 创建文件异常]", type, e);
                }finally {
                    countDownLatch.countDown();
                }
            });
        }

        try {
            if (!countDownLatch.await(1, TimeUnit.MINUTES))
                throw exception(EXPRESSAGE_DOWNLOAD_FILE_REFUSE);
        } catch (InterruptedException e) {
            log.error("下载等待超时：", e);
            throw exception(EXPRESSAGE_DOWNLOAD_FILE_REFUSE);
        }

        int size = results.size();
        if (size == 0)
            throw exception(EXPRESSAGE_DOWNLOAD_FILE_REFUSE);
        if (size == 1)
            return results.get(0);

        // 反之，将所有文件打成压缩包
        File zip = ZipUtil.zip(tempZipDirFile);

        try {
            return fileApi.createFile(FileUtil.readBytes(zip), "download.zip");
        }catch (Throwable e){
            log.error("文件压缩异常：", e);
            throw exception(EXPRESSAGE_DOWNLOAD_FILE_ERROR);
        }finally {
            // 删除压缩包
            FileUtil.del(zip);
            // 删除临时目录
            FileUtil.del(tempZipDirFile);
        }
    }

    private String getNormal(String value){
        return value == null ? "" : value;
    }

    @Override
    @Transactional
    public Long appCreateExpressage(AppExpressageSaveReqVO createReqVO, Long userId) {
        ExpressageDO expressage = BeanUtils.toBean(createReqVO, ExpressageDO.class);
        expressage.setUserId(userId);
        List<AppExpressageGoodsSaveReqVO> goods = createReqVO.getGoods();

        CREATE_LOCK.lock();
        try {
            // 获取当前订单号，并递增
            String nextOrderNoStr = configService.updateNextOrderNo(null);
            expressage.setOrderNo("RES" + nextOrderNoStr);

            // 插入寄件订单
            expressageMapper.insert(expressage);
            Long expressageId = expressage.getId();

            // 插入寄件货物信息
            for (AppExpressageGoodsSaveReqVO good : goods) {
                ExpressageGoodsDO goodsDO = BeanUtils.toBean(good, ExpressageGoodsDO.class);
                goodsDO.setExpressageId(expressageId);
                expressageGoodsMapper.insert(goodsDO);
            }

            return expressageId;
        } catch (Throwable e) {
            log.error("寄件信息创建失败：", e);
            throw exception(EXPRESSAGE_CREATE_ERROR);
        } finally {
            if (CREATE_LOCK.isLocked()) {
                CREATE_LOCK.unlock();
            }
        }
    }

    @Override
    @Transactional
    public void appDeleteExpressage(Long id, Long userId) {
        ExpressageDO expressageDO = getExpressageValidate(id);

        // 判断审核状态是否是 已取消
        if (2 != expressageDO.getAuditStatus())
            throw exception(EXPRESSAGE_CAN_NOT_DELETE);

        // 判断是否是当前用户的
        if (!userId.equals(expressageDO.getUserId()))
            throw exception(NO_PERMISSION);

        // 删除货品
        expressageGoodsMapper.delete(ExpressageGoodsDO::getExpressageId, id);
        // 删除寄件订单
        expressageMapper.deleteById(id);
    }

    @Override
    public AppExpressageRespVO appGetExpressage(Long id, Long userId) {
        ExpressageDO expressageDO = getExpressageValidate(id);

        // 判断是否是当前用户的
        if (!userId.equals(expressageDO.getUserId()))
            throw exception(NO_PERMISSION);
        AppExpressageRespVO respVO = BeanUtils.toBean(expressageDO, AppExpressageRespVO.class);

        List<ExpressageGoodsDO> goods = expressageGoodsMapper.selectList(ExpressageGoodsDO::getExpressageId, id);
        respVO.setGoods(BeanUtils.toBean(goods, AppExpressageGoodsRespVO.class));

        return respVO;
    }

    @Override
    public PageResult<AppExpressageSimpleRespVO> appGetExpressagePage(AppExpressagePageReqVO pageReqVO, Long userId) {
        PageResult<AppExpressageSimpleRespVO> pageResult = expressageMapper.appGetExpressagePage(pageReqVO, userId);

        // 遍历寄件信息
        for (AppExpressageSimpleRespVO respVO : pageResult.getList()) {
            // 获取寄件的货物信息
            List<ExpressageGoodsDO> goods = expressageGoodsMapper.selectList(ExpressageGoodsDO::getExpressageId, respVO.getId());

            respVO.setGoodsCls(goods.size())
                    .setGoodsCount(goods.stream().mapToLong(ExpressageGoodsDO::getNum).sum())
                    .setGoodsTotalPrice(BigDecimal.valueOf(goods.stream().mapToDouble(o -> o.getTotalPrice().doubleValue()).sum()));
        }
        return pageResult;
    }

    @Override
    public Long getExpressageCountByUserId(Long userId) {
        return expressageMapper.getExpressageCountByUserId(userId);
    }

    @Override
    public void exportExpressageExcel(ExpressagePageReqVO pageReqVO, HttpServletResponse response) throws IOException {
        // 自定义头部标题
        List<String> headList1 = CollUtil.newArrayList("运单号", "下单时间", "快递状态", "操作时间", "下单人信息", "", "", "收件国家", "运输方式", "是否报关", "寄件方信息", "", "", "", "", "收件方信息", "", "", "", "", "结款方式", "客户账号", "快递单号", "发票号");
        List<String> headList2 = CollUtil.newArrayList("", "", "", "", "ID", "昵称", "手机号", "", "", "", "姓名", "手机号", "详细地址", "公司抬头", "所属部门", "姓名", "手机号", "详细地址", "公司抬头", "所属部门", "", "", "", "", "货物名", "单价", "数量", "总值");
        cn.hutool.poi.excel.ExcelWriter writer = ExcelUtil.getWriter(/*"C:\\Users\\123\\Desktop\\Self/writeBeanTest.xlsx"*/);

        // 获取样式
        StyleSet style = writer.getStyleSet();
        Font headFont = writer.createFont();
        headFont.setBold(true);
        headFont.setFontHeightInPoints((short) 16);
        //重点，设置中文字体
        headFont.setFontName("楷体");
        style.setWrapText();
        // 设置头部标题字体与背景色
        style.getHeadCellStyle().setFont(headFont);
        style.getHeadCellStyle().setFillForegroundColor(IndexedColors.WHITE.getIndex());

        // 写入头部标题
        writer.writeHeadRow(headList1);
        writer.writeHeadRow(headList2);

        // 获取所有数据
        List<ExpressageRespVO> expressageList = this.getExpressagePage(pageReqVO).getList();

        // 记录起始行
        int rowNum = 2;
        for (ExpressageRespVO expressage : expressageList) {
            // 获取当前订单的货物
            List<ExpressageGoodsDO> goods = expressageGoodsMapper.selectList(ExpressageGoodsDO::getExpressageId, expressage.getId());

            // 快递状态
            String auditStatus = DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.BUSINESS_EXPRESSAGE_AUDIT_STATUS, expressage.getAuditStatus());
            // 是否报关
            String customsFiling = Objects.isNull(expressage.getCustomsFiling()) || expressage.getCustomsFiling().equals(0) ? "不报关" : "报关";
            // 结款方式
            String payMethod = DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.BUSINESS_EXPRESSAGE_PAY_METHOD, expressage.getPayMethod());

            int goodsSize = goods.size();
            for (ExpressageGoodsDO good : goods) {

                // 构建该行每个单元格内容
                ArrayList<Object> rowPlanContents = CollUtil.newArrayList(expressage.getOrderNo(), expressage.getCreateTime(), auditStatus,
                        expressage.getAuditDatetime(), expressage.getUserId(), expressage.getNickname(), expressage.getMobile(),
                        expressage.getCountry(), expressage.getShippingType(), customsFiling,
                        expressage.getSendName(), expressage.getSendMobile(), expressage.getSendAddress(),
                        expressage.getSendCompany(), expressage.getSendDept(),
                        expressage.getRecipientsName(), expressage.getRecipientsMobile(), expressage.getRecipientsAddress(),
                        expressage.getRecipientsCompany(), expressage.getRecipientsDept(),
                        payMethod, expressage.getCustomerAccount(), expressage.getExpressageOrder(), expressage.getInvoiceNum(),
                        good.getName(), good.getUnitPrice(), good.getNum(), good.getTotalPrice());
                writer.writeRow(rowPlanContents);
            }

            int lastRowNum = rowNum + goodsSize - 1;
            writer.merge(rowNum, lastRowNum, 0, 0, expressage.getOrderNo(), false);
            writer.merge(rowNum, lastRowNum, 1, 1, expressage.getCreateTime(), false);
            writer.merge(rowNum, lastRowNum, 2, 2, auditStatus, false);
            writer.merge(rowNum, lastRowNum, 3, 3, expressage.getAuditDatetime(), false);
            writer.merge(rowNum, lastRowNum, 4, 4, expressage.getUserId(), false);
            writer.merge(rowNum, lastRowNum, 5, 5, expressage.getNickname(), false);
            writer.merge(rowNum, lastRowNum, 6, 6, expressage.getMobile(), false);
            writer.merge(rowNum, lastRowNum, 7, 7, expressage.getCountry(), false);
            writer.merge(rowNum, lastRowNum, 8, 8, expressage.getShippingType(), false);
            writer.merge(rowNum, lastRowNum, 9, 9, customsFiling, false);
            writer.merge(rowNum, lastRowNum, 10, 10, expressage.getSendName(), false);
            writer.merge(rowNum, lastRowNum, 11, 11, expressage.getSendMobile(), false);
            writer.merge(rowNum, lastRowNum, 12, 12, expressage.getSendAddress(), false);
            writer.merge(rowNum, lastRowNum, 13, 13, expressage.getSendCompany(), false);
            writer.merge(rowNum, lastRowNum, 14, 14, expressage.getSendDept(), false);
            writer.merge(rowNum, lastRowNum, 15, 15, expressage.getRecipientsName(), false);
            writer.merge(rowNum, lastRowNum, 16, 16, expressage.getRecipientsMobile(), false);
            writer.merge(rowNum, lastRowNum, 17, 17, expressage.getRecipientsAddress(), false);
            writer.merge(rowNum, lastRowNum, 18, 18, expressage.getRecipientsCompany(), false);
            writer.merge(rowNum, lastRowNum, 19, 19, expressage.getRecipientsDept(), false);
            writer.merge(rowNum, lastRowNum, 20, 20, payMethod, false);
            writer.merge(rowNum, lastRowNum, 21, 21, expressage.getCustomerAccount(), false);
            writer.merge(rowNum, lastRowNum, 22, 22, expressage.getExpressageOrder(), false);
            writer.merge(rowNum, lastRowNum, 23, 23, expressage.getInvoiceNum(), false);
            rowNum = lastRowNum + 1;
        }

        // 合并标题单元格
        writer.merge(0, 1, 0, 0, "运单号", true);
        writer.merge(0, 1, 1, 1, "下单时间", true);
        writer.merge(0, 1, 2, 2, "快递状态", true);
        writer.merge(0, 1, 3, 3, "操作时间", true);
        writer.merge(0, 0, 4, 6, "下单人信息", true);
        writer.merge(0, 1, 7, 7, "收件国家", true);
        writer.merge(0, 1, 8, 8, "运输方式", true);
        writer.merge(0, 1, 9, 9, "是否报关", true);
        writer.merge(0, 0, 10, 14, "寄件方信息", true);
        writer.merge(0, 0, 15, 19, "收件方信息", true);
        writer.merge(0, 1, 20, 20, "结款方式", true);
        writer.merge(0, 1, 21, 21, "客户账号", true);
        writer.merge(0, 1, 22, 22, "快递单号", true);
        writer.merge(0, 1, 23, 23, "发票号", true);
        writer.merge(0, 0, 24, 27, "货物信息", true);

        writer.setOnlyAlias(true);
        writer.autoSizeColumnAll();

        writer.setColumnWidth(0, 13);
        writer.setColumnWidth(1, 15);
        writer.setColumnWidth(2, 13);
        writer.setColumnWidth(3, 15);
        writer.setColumnWidth(4, 8);
        writer.setColumnWidth(5, 12);
        writer.setColumnWidth(6, 13);
        writer.setColumnWidth(7, 13);
        writer.setColumnWidth(8, 13);
        writer.setColumnWidth(9, 13);
        writer.setColumnWidth(10, 16);
        writer.setColumnWidth(11, 20);
        writer.setColumnWidth(12, 25);
        writer.setColumnWidth(13, 25);
        writer.setColumnWidth(14, 23);
        writer.setColumnWidth(15, 16);
        writer.setColumnWidth(16, 20);
        writer.setColumnWidth(17, 25);
        writer.setColumnWidth(18, 25);
        writer.setColumnWidth(19, 23);
        writer.setColumnWidth(20, 13);
        writer.setColumnWidth(21, 15);
        writer.setColumnWidth(22, 25);
        writer.setColumnWidth(23, 16);
        writer.setColumnWidth(24, 20);
        writer.setColumnWidth(25, 10);
        writer.setColumnWidth(26, 10);
        writer.setColumnWidth(27, 12);

        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        // test.xls是弹出下载对话框的文件名，不能为中文，中文请自行编码
        response.setHeader("Content-Disposition", "attachment;filename=题目信息.xls");
        ServletOutputStream out = response.getOutputStream();

        writer.flush(out, true);

        // 关闭writer，释放内存
        writer.close();
        // 此处记得关闭输出Servlet流
        IoUtil.close(out);
    }
}